// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_AST_AST_SOURCE_RANGES_H_
#define V8_AST_AST_SOURCE_RANGES_H_

#include "src/ast/ast.h"
#include "src/zone/zone-containers.h"

namespace v8 {
namespace internal {

    // Specifies a range within the source code. {start} is 0-based and inclusive,
    // {end} is 0-based and exclusive.
    struct SourceRange {
        SourceRange()
            : SourceRange(kNoSourcePosition, kNoSourcePosition)
        {
        }
        SourceRange(int start, int end)
            : start(start)
            , end(end)
        {
        }
        bool IsEmpty() const { return start == kNoSourcePosition; }
        static SourceRange Empty() { return SourceRange(); }
        static SourceRange OpenEnded(int32_t start)
        {
            return SourceRange(start, kNoSourcePosition);
        }
        static SourceRange ContinuationOf(const SourceRange& that,
            int end = kNoSourcePosition)
        {
            return that.IsEmpty() ? Empty() : SourceRange(that.end, end);
        }
        int32_t start, end;
    };

// The list of ast node kinds that have associated source ranges. Note that this
// macro is not undefined at the end of this file.
#define AST_SOURCE_RANGE_LIST(V) \
    V(BinaryOperation)           \
    V(Block)                     \
    V(CaseClause)                \
    V(Conditional)               \
    V(IfStatement)               \
    V(IterationStatement)        \
    V(JumpStatement)             \
    V(NaryOperation)             \
    V(Suspend)                   \
    V(SwitchStatement)           \
    V(Throw)                     \
    V(TryCatchStatement)         \
    V(TryFinallyStatement)

    enum class SourceRangeKind {
        kBody,
        kCatch,
        kContinuation,
        kElse,
        kFinally,
        kRight,
        kThen,
    };

    class AstNodeSourceRanges : public ZoneObject {
    public:
        virtual ~AstNodeSourceRanges() = default;
        virtual SourceRange GetRange(SourceRangeKind kind) = 0;
        virtual bool HasRange(SourceRangeKind kind) = 0;
        virtual void RemoveContinuationRange() { UNREACHABLE(); }
    };

    class BinaryOperationSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit BinaryOperationSourceRanges(const SourceRange& right_range)
            : right_range_(right_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            return right_range_;
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kRight;
        }

    private:
        SourceRange right_range_;
    };

    class ContinuationSourceRanges : public AstNodeSourceRanges {
    public:
        explicit ContinuationSourceRanges(int32_t continuation_position)
            : continuation_position_(continuation_position)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            return SourceRange::OpenEnded(continuation_position_);
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kContinuation;
        }

        void RemoveContinuationRange() override
        {
            DCHECK(HasRange(SourceRangeKind::kContinuation));
            continuation_position_ = kNoSourcePosition;
        }

    private:
        int32_t continuation_position_;
    };

    class BlockSourceRanges final : public ContinuationSourceRanges {
    public:
        explicit BlockSourceRanges(int32_t continuation_position)
            : ContinuationSourceRanges(continuation_position)
        {
        }
    };

    class CaseClauseSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit CaseClauseSourceRanges(const SourceRange& body_range)
            : body_range_(body_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            return body_range_;
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kBody;
        }

    private:
        SourceRange body_range_;
    };

    class ConditionalSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit ConditionalSourceRanges(const SourceRange& then_range,
            const SourceRange& else_range)
            : then_range_(then_range)
            , else_range_(else_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            switch (kind) {
            case SourceRangeKind::kThen:
                return then_range_;
            case SourceRangeKind::kElse:
                return else_range_;
            default:
                UNREACHABLE();
            }
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kThen || kind == SourceRangeKind::kElse;
        }

    private:
        SourceRange then_range_;
        SourceRange else_range_;
    };

    class IfStatementSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit IfStatementSourceRanges(const SourceRange& then_range,
            const SourceRange& else_range)
            : then_range_(then_range)
            , else_range_(else_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            switch (kind) {
            case SourceRangeKind::kElse:
                return else_range_;
            case SourceRangeKind::kThen:
                return then_range_;
            case SourceRangeKind::kContinuation: {
                if (!has_continuation_)
                    return SourceRange::Empty();
                const SourceRange& trailing_range = else_range_.IsEmpty() ? then_range_ : else_range_;
                return SourceRange::ContinuationOf(trailing_range);
            }
            default:
                UNREACHABLE();
            }
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kThen || kind == SourceRangeKind::kElse || kind == SourceRangeKind::kContinuation;
        }

        void RemoveContinuationRange() override
        {
            DCHECK(HasRange(SourceRangeKind::kContinuation));
            has_continuation_ = false;
        }

    private:
        SourceRange then_range_;
        SourceRange else_range_;
        bool has_continuation_ = true;
    };

    class IterationStatementSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit IterationStatementSourceRanges(const SourceRange& body_range)
            : body_range_(body_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            switch (kind) {
            case SourceRangeKind::kBody:
                return body_range_;
            case SourceRangeKind::kContinuation:
                if (!has_continuation_)
                    return SourceRange::Empty();
                return SourceRange::ContinuationOf(body_range_);
            default:
                UNREACHABLE();
            }
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kBody || kind == SourceRangeKind::kContinuation;
        }

        void RemoveContinuationRange() override
        {
            DCHECK(HasRange(SourceRangeKind::kContinuation));
            has_continuation_ = false;
        }

    private:
        SourceRange body_range_;
        bool has_continuation_ = true;
    };

    class JumpStatementSourceRanges final : public ContinuationSourceRanges {
    public:
        explicit JumpStatementSourceRanges(int32_t continuation_position)
            : ContinuationSourceRanges(continuation_position)
        {
        }
    };

    class NaryOperationSourceRanges final : public AstNodeSourceRanges {
    public:
        NaryOperationSourceRanges(Zone* zone, const SourceRange& range)
            : ranges_(zone)
        {
            AddRange(range);
        }

        SourceRange GetRangeAtIndex(size_t index)
        {
            DCHECK(index < ranges_.size());
            return ranges_[index];
        }

        void AddRange(const SourceRange& range) { ranges_.push_back(range); }
        size_t RangeCount() const { return ranges_.size(); }

        SourceRange GetRange(SourceRangeKind kind) override { UNREACHABLE(); }
        bool HasRange(SourceRangeKind kind) override { return false; }

    private:
        ZoneVector<SourceRange> ranges_;
    };

    class SuspendSourceRanges final : public ContinuationSourceRanges {
    public:
        explicit SuspendSourceRanges(int32_t continuation_position)
            : ContinuationSourceRanges(continuation_position)
        {
        }
    };

    class SwitchStatementSourceRanges final : public ContinuationSourceRanges {
    public:
        explicit SwitchStatementSourceRanges(int32_t continuation_position)
            : ContinuationSourceRanges(continuation_position)
        {
        }
    };

    class ThrowSourceRanges final : public ContinuationSourceRanges {
    public:
        explicit ThrowSourceRanges(int32_t continuation_position)
            : ContinuationSourceRanges(continuation_position)
        {
        }
    };

    class TryCatchStatementSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit TryCatchStatementSourceRanges(const SourceRange& catch_range)
            : catch_range_(catch_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            switch (kind) {
            case SourceRangeKind::kCatch:
                return catch_range_;
            case SourceRangeKind::kContinuation:
                if (!has_continuation_)
                    return SourceRange::Empty();
                return SourceRange::ContinuationOf(catch_range_);
            default:
                UNREACHABLE();
            }
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kCatch || kind == SourceRangeKind::kContinuation;
        }

        void RemoveContinuationRange() override
        {
            DCHECK(HasRange(SourceRangeKind::kContinuation));
            has_continuation_ = false;
        }

    private:
        SourceRange catch_range_;
        bool has_continuation_ = true;
    };

    class TryFinallyStatementSourceRanges final : public AstNodeSourceRanges {
    public:
        explicit TryFinallyStatementSourceRanges(const SourceRange& finally_range)
            : finally_range_(finally_range)
        {
        }

        SourceRange GetRange(SourceRangeKind kind) override
        {
            DCHECK(HasRange(kind));
            switch (kind) {
            case SourceRangeKind::kFinally:
                return finally_range_;
            case SourceRangeKind::kContinuation:
                if (!has_continuation_)
                    return SourceRange::Empty();
                return SourceRange::ContinuationOf(finally_range_);
            default:
                UNREACHABLE();
            }
        }

        bool HasRange(SourceRangeKind kind) override
        {
            return kind == SourceRangeKind::kFinally || kind == SourceRangeKind::kContinuation;
        }

        void RemoveContinuationRange() override
        {
            DCHECK(HasRange(SourceRangeKind::kContinuation));
            has_continuation_ = false;
        }

    private:
        SourceRange finally_range_;
        bool has_continuation_ = true;
    };

    // Maps ast node pointers to associated source ranges. The parser creates these
    // mappings and the bytecode generator consumes them.
    class SourceRangeMap final : public ZoneObject {
    public:
        explicit SourceRangeMap(Zone* zone)
            : map_(zone)
        {
        }

        AstNodeSourceRanges* Find(ZoneObject* node)
        {
            auto it = map_.find(node);
            if (it == map_.end())
                return nullptr;
            return it->second;
        }

// Type-checked insertion.
#define DEFINE_MAP_INSERT(type)                         \
    void Insert(type* node, type##SourceRanges* ranges) \
    {                                                   \
        DCHECK_NOT_NULL(node);                          \
        map_.emplace(node, ranges);                     \
    }
        AST_SOURCE_RANGE_LIST(DEFINE_MAP_INSERT)
#undef DEFINE_MAP_INSERT

    private:
        ZoneMap<ZoneObject*, AstNodeSourceRanges*> map_;
    };

} // namespace internal
} // namespace v8

#endif // V8_AST_AST_SOURCE_RANGES_H_
